package com.topchain.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.topchain.constant.CommonConstant;
import com.topchain.entity.RootCert;
import com.topchain.entity.UserCert;
import com.topchain.entity.cert.GenCert;
import com.topchain.entity.cert.RSA;
import com.topchain.common.ResponseCodeEnum;
import com.topchain.common.ResultMessage;
import com.topchain.vo.RootCertReq;
import com.topchain.vo.UserCertReq;
import com.topchain.utils.CryptoUtil;
import com.topchain.service.RootCertService;
import com.topchain.service.UserCertService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.Base64;
import java.util.Objects;

/**
 * @DESCRIPTION: 生成标准根证书, 由标准根证书生成用户证书
 * @USER: shg
 * @DATE: 2023/4/10 15:37
 */
@Slf4j
@ApiIgnore
@RestController
@RequestMapping("/generate")
public class GenStandardCertController {

    public static final String SIGN_ALGORITHM = "sha256withRSA";
    private final RootCertService rootCertService;
    private final UserCertService userCertService;

    public GenStandardCertController(RootCertService rootCertService, UserCertService userCertService) {
        this.rootCertService = rootCertService;
        this.userCertService = userCertService;
    }

    @ApiOperation(value = "生成标准根证书",hidden = true)
    @PostMapping(value = "/rootCert")
    public ResultMessage GenStandardCaRootCert(@RequestBody RootCertReq cert) throws Exception {
        log.info("-----------------生成标准根证书-----------------");
        RSA rsa = new RSA();
        KeyPair keyPair = rsa.genKeyPair(2048);
        X509Certificate caRootCert = new GenCert().genStandardCaRootCert(true, keyPair, SIGN_ALGORITHM, cert.getDn(), cert.getValidDate(), "www.rsa.com", "root@qq.com");

        RootCert rootCert = new RootCert();
        // 自定义的根证书标识名
        rootCert.setCertName(cert.getCertName());
        rootCert.setVersion(String.valueOf(caRootCert.getVersion()));
        rootCert.setSubjectDn(caRootCert.getSubjectDN().toString());
        rootCert.setIssuerDn(caRootCert.getIssuerDN().toString());
        rootCert.setNotBefore(caRootCert.getNotBefore().toLocaleString());
        rootCert.setNotAfter(caRootCert.getNotAfter().toLocaleString());
        rootCert.setKeyAlg(getCertAlgorithm(caRootCert));
        rootCert.setKeyLength(getKeyLength(caRootCert));
        rootCert.setSignAlg(caRootCert.getSigAlgName());
        rootCert.setSerialNumber(getCertificateSerialNumber(caRootCert.getSerialNumber()));
        rootCert.setPrivateKey(CryptoUtil.convertBase64Pem(keyPair.getPrivate()));
        rootCert.setPublicKey(CryptoUtil.convertBase64Pem(keyPair.getPublic()));
        rootCert.setCertBlob(CryptoUtil.convertBase64Pem(caRootCert));
        rootCert.setSignature(Base64.getEncoder().encodeToString(caRootCert.getSignature()));
        rootCertService.save(rootCert);
        return ResultMessage.success("生成标准根证书成功", true);
    }

    @ApiOperation(value = "生成标准用户证书",hidden = true)
    @PostMapping(value = "/userCert")
    public ResultMessage GenCertWithCaSign(@RequestBody UserCertReq userCertReq) throws Exception {
        log.info("-----------------生成标准用户证书-----------------");
        String certName = userCertReq.getCertName();
        LambdaQueryWrapper<UserCert> userCertLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userCertLambdaQueryWrapper.eq(UserCert::getCertName, certName);
        UserCert userCertFromDB = userCertService.getOne(userCertLambdaQueryWrapper);
        if (!Objects.isNull(userCertFromDB))
            return ResultMessage.errorResult(ResponseCodeEnum.SYSTEM_EXCEPTION.getCode(), "用户证书标识名已存在");

        RSA rsa = new RSA();
        KeyPair keyPair = rsa.genKeyPair(2048);
        String rootCertName = userCertReq.getRootCertName();
        if (StringUtils.isBlank(rootCertName)) rootCertName = "rootCert";
        LambdaQueryWrapper<RootCert> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RootCert::getCertName, rootCertName);
        RootCert rootCert = rootCertService.getOne(queryWrapper);
        KeyPair keyPair1 = CryptoUtil.parseKeyPairFromPemStr(rootCert.getPrivateKey(), "");

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
        X509Certificate caRootCert = (X509Certificate) certificateFactory.generateCertificate(Files.newInputStream(Paths.get("StandardCaRootCert.pem")));
        X509Certificate certificate = new GenCert().genCertWithCaSign(caRootCert, keyPair1.getPrivate(), keyPair,
                SIGN_ALGORITHM, userCertReq.getDn(), userCertReq.getValidDate(), "www.rsa-user.com", "user@qq.com");
        
        UserCert userCert = new UserCert();
        // 自定义的用户证书标识名
        userCert.setCertName(userCertReq.getCertName());
        userCert.setVersion(String.valueOf(certificate.getVersion()));
        userCert.setSubjectDn(certificate.getSubjectDN().toString());
        userCert.setIssuerDn(certificate.getIssuerDN().toString());
        userCert.setNotBefore(certificate.getNotBefore().toLocaleString());
        userCert.setNotAfter(certificate.getNotAfter().toLocaleString());
        userCert.setKeyAlg(getCertAlgorithm(certificate));
        userCert.setKeyLength(getKeyLength(certificate));
        userCert.setSignAlg(certificate.getSigAlgName());
        userCert.setSerialNumber(getCertificateSerialNumber(certificate.getSerialNumber()));
        userCert.setPrivateKey(CryptoUtil.convertBase64Pem(keyPair.getPrivate()));
        userCert.setPublicKey(CryptoUtil.convertBase64Pem(keyPair.getPublic()));
        userCert.setCertBlob(CryptoUtil.convertBase64Pem(certificate));
        userCert.setSignature(Base64.getEncoder().encodeToString(certificate.getSignature()));
        userCert.setRootCertId(rootCert.getId());
        userCertService.save(userCert);
        return ResultMessage.success("由根证书签发用户证书成功", true);
    }

    private static String getCertAlgorithm(X509Certificate cert) {
        PublicKey publicKey = cert.getPublicKey();
        String algorithm = publicKey.getAlgorithm();
        if ("EC".equals(algorithm)) {
            if (publicKey instanceof ECPublicKey) {
                BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
                ECParameterSpec ecParameterSpec = bcecPublicKey.getParams();
                ECNamedCurveSpec ecNamedCurveSpec = (ECNamedCurveSpec) ecParameterSpec;
                String curveSpecName = ecNamedCurveSpec.getName();
                if ("sm2p256v1".equals(curveSpecName)) {
                    return CommonConstant.SM2;
                }
            } else {
                return algorithm;
            }
        } else if (CommonConstant.RSA.equals(algorithm)) {
            return CommonConstant.RSA;
        } else {
            return algorithm;
        }
        return null;
    }

    private static int getKeyLength(X509Certificate cert) {
        int publicKeyLength = 0;
        PublicKey publicKey = cert.getPublicKey();
        if (publicKey instanceof RSAPublicKey) {
            publicKeyLength = ((RSAPublicKey) publicKey).getModulus().bitLength();
        } else if (publicKey instanceof ECPublicKey) {
            return CommonConstant.SM2_KEY_SIZE;
        }
        return publicKeyLength;
    }

    private static String getCertificateSerialNumber(BigInteger serialNumber) {
        String serialNumberHex = serialNumber.toString(16);
        if (serialNumberHex.length() % 2 == 1) {
            serialNumberHex = "0" + serialNumberHex;
        }
        return serialNumberHex;
    }
}
